package com.wmada.toolkit.common;

import com.sun.istack.internal.NotNull;
import org.apache.commons.lang3.StringUtils;

import java.lang.reflect.Array;
import java.math.BigDecimal;
import java.math.BigInteger;
import java.sql.Time;
import java.sql.Timestamp;
import java.util.*;
import java.util.regex.Pattern;

/**
 * @author will.ma
 * @date 2020-11-11
 */
public class TypeUtils {

    /**
     * The constant EMAIL_VALIDATOR.
     */
    public static final String  EMAIL_VALIDATOR = "\\w+([-+.]\\w+)*@\\w+([-.]\\w+)*\\.\\w+([-.]\\w+)*";
    /**
     * The constant URL_VALIDATOR.
     */
    public static final String  URL_VALIDATOR   = "^(?:(http|ftp|https)://)(([a-zA-Z0-9\\._-]+\\.[a-zA-Z]{2,6})|([0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3}\\.[0-9]{1,3})|(localhost))(?::([0-9]{1,5}))?(/[a-zA-Z0-9&%_\\./+=\\?\\-~#:\\u4E00-\\u9FFF]*)?$";
    /**
     * The constant pattern.
     */
    public static final Pattern NUMBER_PATTERN  = Pattern.compile("^[+-]?\\d+(\\.\\d*)?$");

    /**
     * Is email boolean.
     *
     * @param email the email
     * @return the boolean
     */
    static public boolean isEmail(String email) {
        return email != null && email.matches(EMAIL_VALIDATOR);
    }

    /**
     * Is url boolean.
     *
     * @param url the url
     * @return the boolean
     */
    static public boolean isUrl(String url) {
        return url != null && url.matches(URL_VALIDATOR);
    }

    /**
     * Is empty boolean.
     *
     * @param object the object
     * @return the boolean
     */
    public static boolean isEmpty(Object object) {
        if (object == null) {
            return true;
        } else if (object instanceof CharSequence) {
            return StringUtils.isBlank((CharSequence) object);
        } else if (object instanceof Collection) {
            return ((Collection<?>) object).isEmpty();
        } else if (object instanceof Map) {
            return ((Map<?, ?>) object).isEmpty();
        } else if (object.getClass().isArray()) {
            return Array.getLength(object) == 0;
        } else {
            return false;
        }
    }

    /**
     * Is any empty boolean.
     *
     * @param values the values
     * @return the boolean
     */
    public static boolean isAnyEmpty(Object... values) {
        for (Object value : values) {
            if (isEmpty(value)) {
                return true;
            }
        }
        return false;
    }

    /**
     * Not empty boolean.
     *
     * @param object the object
     * @return the boolean
     */
    public static boolean notEmpty(Object object) {
        if (object == null) {
            return false;
        } else if (object instanceof CharSequence) {
            return StringUtils.isNotBlank((CharSequence) object);
        } else if (object instanceof Collection) {
            return !((Collection<?>) object).isEmpty();
        } else if (object instanceof Map) {
            return !((Map<?, ?>) object).isEmpty();
        } else if (object.getClass().isArray()) {
            return Array.getLength(object) != 0;
        } else {
            return true;
        }
    }


    /**
     * Is out of bounds boolean.
     *
     * @param collection the collection
     * @param index      the index
     * @return the boolean
     */
    public static boolean isOutOfBounds(Collection<?> collection, int index) {
        return index >= collection.size();
    }

    /**
     * Is out of bounds boolean.
     *
     * @param map   the map
     * @param index the index
     * @return the boolean
     */
    public static boolean isOutOfBounds(Map<?, ?> map, int index) {
        return index >= map.size();
    }

    /**
     * Is integer boolean.
     *
     * @param obj the obj
     * @return the boolean
     */
    public static boolean isInteger(Object obj) {
        return obj instanceof Integer || isInteger(String.valueOf(obj));
    }

    /**
     * Is integer boolean.
     *
     * @param str the str
     * @return the boolean
     */
    public static boolean isInteger(String str) {
        try {
            Integer.parseInt(str);
            return true;
        } catch (NumberFormatException e) {
            return false;
        }
    }

    /**
     * Is number boolean.
     *
     * @param str the str
     * @return the boolean
     */
    public static boolean isNumber(String str) {
        return NUMBER_PATTERN.matcher(str).find();
    }

    public static boolean isTrue(Object value) {
        return value != null && (value instanceof Boolean ? (Boolean) value : ConvertUtils.convertType(value, Boolean.class));
    }

    public static boolean isFalse(Object value) {
        return value == null || (value instanceof Boolean ? !(Boolean) value : !ConvertUtils.convertType(value, Boolean.class));
    }

    static final Set<Class<?>>         BASIC_CLASSES     = new HashSet<>();
    static final Map<String, Class<?>> BASIC_CLASS_NAMES = new HashMap<>();

    static {
        TypeUtils.BASIC_CLASSES.add(Byte.class);
        TypeUtils.BASIC_CLASSES.add(Short.class);
        TypeUtils.BASIC_CLASSES.add(Integer.class);
        TypeUtils.BASIC_CLASSES.add(Long.class);
        TypeUtils.BASIC_CLASSES.add(Character.class);
        TypeUtils.BASIC_CLASSES.add(Float.class);
        TypeUtils.BASIC_CLASSES.add(Double.class);
        TypeUtils.BASIC_CLASSES.add(Boolean.class);

        TypeUtils.BASIC_CLASSES.add(String.class);

        TypeUtils.BASIC_CLASSES.add(BigInteger.class);
        TypeUtils.BASIC_CLASSES.add(BigDecimal.class);

        TypeUtils.BASIC_CLASSES.add(Date.class);
        TypeUtils.BASIC_CLASSES.add(java.sql.Date.class);
        TypeUtils.BASIC_CLASSES.add(Timestamp.class);
        TypeUtils.BASIC_CLASSES.add(Time.class);

        TypeUtils.BASIC_CLASSES.add(byte.class);
        TypeUtils.BASIC_CLASSES.add(short.class);
        TypeUtils.BASIC_CLASSES.add(int.class);
        TypeUtils.BASIC_CLASSES.add(long.class);
        TypeUtils.BASIC_CLASSES.add(char.class);
        TypeUtils.BASIC_CLASSES.add(float.class);
        TypeUtils.BASIC_CLASSES.add(double.class);
        TypeUtils.BASIC_CLASSES.add(boolean.class);

        TypeUtils.BASIC_CLASSES.forEach(tClass -> TypeUtils.BASIC_CLASS_NAMES.put(tClass.getName(), tClass));

        TypeUtils.BASIC_CLASS_NAMES.put("[B", byte[].class);
        TypeUtils.BASIC_CLASS_NAMES.put("[S", short[].class);
        TypeUtils.BASIC_CLASS_NAMES.put("[I", int[].class);
        TypeUtils.BASIC_CLASS_NAMES.put("[J", long[].class);
        TypeUtils.BASIC_CLASS_NAMES.put("[C", char[].class);
        TypeUtils.BASIC_CLASS_NAMES.put("[D", double[].class);
        TypeUtils.BASIC_CLASS_NAMES.put("[F", float[].class);
        TypeUtils.BASIC_CLASS_NAMES.put("[Z", boolean[].class);
    }

    public static <T> boolean isBasic(@NotNull Class<T> tClass) {
        return BASIC_CLASSES.contains(tClass) || tClass.isEnum();
    }
}
